/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { CacheControl } from './CacheControl'
import { ResponseBody } from './ResponseBody'
import { Exchange } from './connection/Exchange'
import { Headers } from './Headers'
import { Protocol } from './Protocol'
import { Request } from './Request'
import { http } from '@kit.NetworkKit';
import { StatusLine } from './http/StatusLine'
import { List } from '@kit.ArkTS'
import { kotlinCheck, kotlinRequire } from './utils/Utils'
import { Handshake } from './Handshake'
import { Challenge } from './Challenge'
import { parseChallenges } from './http/HttpHeaders'

/**
 * An HTTP response. Instances of this class are not immutable: the response body is a one-shot
 * value that may be consumed only once and then closed. All other properties are immutable.
 *
 * This class implements [Closeable]. Closing it simply closes its response body. See
 * [ResponseBody] for an explanation and examples.
 */
export class Response {
  /**
   * The wire-level request that initiated this HTTP response. This is not necessarily the same
   * request issued by the application:
   *
   * * It may be transformed by the HTTP client. For example, the client may copy headers like
   *   `Content-Length` from the request body.
   * * It may be the request generated in response to an HTTP redirect or authentication
   *   challenge. In this case the request URL may be different than the initial request URL.
   */
  #request: Request
  get request(): Request {
    return this.#request
  }
  /** Returns the HTTP protocol, such as [Protocol.HTTP_1_1] or [Protocol.HTTP_1_0]. */
  #protocol: Protocol
  get protocol(): Protocol {
    return this.#protocol
  }


  /** Returns the HTTP status message. */
  #message: string
  get message(): string {
    return this.#message
  }

  /** Returns the HTTP status code. */
  #code: number
  get code(): number {
    return this.#code
  }

  /**
   * Returns the TLS handshake of the connection that carried this response, or null if the
   * response was received without TLS.
   */
  #handshake: Handshake
  get handshake(): Handshake {
    return this.#handshake
  }

  /** Returns the HTTP headers. */
  #headers: Headers
  get headers(): Headers {
    return this.#headers
  }

  /**
   * Returns a non-null value if this response was passed to [Callback.onResponse] or returned
   * from [Call.execute]. Response bodies must be [closed][ResponseBody] and may
   * be consumed only once.
   *
   * This always returns null on responses returned from [cacheResponse], [networkResponse],
   * and [priorResponse].
   */
  #body: ResponseBody | null = null
  get body(): ResponseBody | null {
    return this.#body
  }

  /**
   * Returns the raw response received from the network. Will be null if this response didn't use
   * the network, such as when the response is fully cached. The body of the returned response
   * should not be read.
   */
  #networkResponse: Response | null= null
  get networkResponse(): Response | null {
    return this.#networkResponse
  }

  /**
   * Returns the raw response received from the cache. Will be null if this response didn't use
   * the cache. For conditional get requests the cache response and network response may both be
   * non-null. The body of the returned response should not be read.
   */
  #cacheResponse: Response | null= null
  get cacheResponse(): Response | null {
    return this.#cacheResponse
  }

  /**
   * Returns the response for the HTTP redirect or authorization challenge that triggered this
   * response, or null if this response wasn't triggered by an automatic retry. The body of the
   * returned response should not be read because it has already been consumed by the redirecting
   * client.
   */
  #priorResponse: Response | null= null
  get priorResponse(): Response | null {
    return this.#priorResponse
  }

  /**
   * Returns a [timestamp][System.currentTimeMillis] taken immediately before OkHttp
   * transmitted the initiating request over the network. If this response is being served from the
   * cache then this is the timestamp of the original request.
   */
  #sentRequestAtMillis: number
  get sentRequestAtMillis(): number {
    return this.#sentRequestAtMillis
  }

  /**
   * Returns a [timestamp][System.currentTimeMillis] taken immediately after OkHttp
   * received this response's headers from the network. If this response is being served from the
   * cache then this is the timestamp of the original response.
   */
  #receivedResponseAtMillis: number
  get receivedResponseAtMillis(): number {
    return this.#receivedResponseAtMillis
  }

  #exchange: Exchange | null = null
  get exchange(): Exchange | null {
    return this.#exchange
  }

  constructor(request: Request, protocol: Protocol, message: string, code: number, handshake: Handshake, headers: Headers, body: ResponseBody | null, networkResponse: Response | null, cacheResponse: Response | null, priorResponse: Response | null, sentRequestAtMillis: number, receivedResponseAtMillis: number, exchange: Exchange | null) {
    this.#request = request
    this.#protocol = protocol
    this.#message = message
    this.#code = code
    this.#handshake = handshake
    this.#headers = headers
    this.#body = body
    this.#networkResponse = networkResponse
    this.#cacheResponse = cacheResponse
    this.#priorResponse = priorResponse
    this.#sentRequestAtMillis = sentRequestAtMillis
    this.#receivedResponseAtMillis = receivedResponseAtMillis
    this.#exchange = exchange
    // other
  }

  private lazyCacheControl: CacheControl | null = null

  /**
   * Returns true if the code is in [200..300), which means the request was successfully received,
   * understood, and accepted.
   */
  get isSuccessful(): boolean {
    return this.#code >= 200 && this.#code < 300
  }

  /**
   * Returns the header value for the given name, or defaultValue if not present.
   * @param name - The header name
   * @param defaultValue - The default value to return if header is not found
   * @returns The header value or default value
   */
  header(name: string, defaultValue: string | null = null): string | null {
    const value = this.#headers.get(name)
    return value !== null ? value : defaultValue
  }

  /**
   * Returns the trailers after the HTTP response, which may be empty. It is an error to call this
   * before the entire HTTP response body has been consumed.
   * @returns The HTTP trailers
   * @throws Error if trailers are not available
   */
  trailers(): Headers {
    if (this.#exchange === null) {
      throw new Error("trailers not available")
    }
    return this.#exchange.trailers()
  }

  /**
   * Peeks up to [byteCount] bytes from the response body and returns them as a new response
   * body. If fewer than [byteCount] bytes are in the response body, the full response body is
   * returned. If more than [byteCount] bytes are in the response body, the returned value
   * will be truncated to [byteCount] bytes.
   *
   * It is an error to call this method after the body has been consumed.
   *
   * **Warning:** this method loads the requested bytes into memory. Most applications should set
   * a modest limit on `byteCount`, such as 1 MiB.
   */
  // @Throws(IOException::class)
  peekBody(byteCount: number): ResponseBody { // 暂不实现
    // let peeked = this.#body!!.source().peek()
    // let buffer = Buffer()
    // peeked.request(byteCount)
    // buffer.write(peeked, minOf(byteCount, peeked.buffer.size))
    // return buffer.asResponseBody(this.#body.contentType(), buffer.size)
    return null
  }

  newBuilder() {
    return new Response.Builder(this)
  }

  /** Returns true if this response redirects to another resource. */
  get isRedirect(): boolean {
    switch (this.#code) {
      case StatusLine.HTTP_PERM_REDIRECT:
      case StatusLine.HTTP_TEMP_REDIRECT:
      case http.ResponseCode.MULT_CHOICE:
      case http.ResponseCode.MOVED_PERM:
      case http.ResponseCode.MOVED_TEMP:
      case http.ResponseCode.SEE_OTHER:
        return true
      default:
        return false
    }
  }

  /**
   * Returns the RFC 7235 authorization challenges appropriate for this response's code. If the
   * response code is 401 unauthorized, this returns the "WWW-Authenticate" challenges. If the
   * response code is 407 proxy unauthorized, this returns the "Proxy-Authenticate" challenges.
   * Otherwise this returns an empty list of challenges.
   *
   * If a challenge uses the `token68` variant instead of auth params, there is exactly one
   * auth param in the challenge at key null. Invalid headers and challenges are ignored.
   * No semantic validation is done, for example that `Basic` auth must have a `realm`
   * auth param, this is up to the caller that interprets these challenges.
   */
  challenges(): List<Challenge> { //Challenge
    if (this.#code === http.ResponseCode.UNAUTHORIZED){
      return parseChallenges(this.headers, "WWW-Authenticate")
    }else if (this.#code === http.ResponseCode.PROXY_AUTH){
      return parseChallenges(this.headers, "Proxy-Authenticate")
    }else{
      return new List()
    }
  }

  /**
   * Returns the cache control directives for this response. This is never null, even if this
   * response contains no `Cache-Control` header.
   */
  get cacheControl(): CacheControl {
    let result = this.lazyCacheControl
    if (result === null) {
      result = CacheControl.parse(this.#headers)
      this.lazyCacheControl = result
    }
    return result
  }
  /**
   * Closes the response body. Equivalent to `body().close()`.
   *
   * It is an error to close a response that is not eligible for a body. This includes the
   * responses returned from [cacheResponse], [networkResponse], and [priorResponse].
   * @throws Error if the response is not eligible for a body
   */
  close(): void {
    if (this.#body === null) {
      throw new Error("response is not eligible for a body and must not be closed")
    }
    this.#body.close()
  }

  /**
   * Returns a string representation of this response.
   * @returns A string containing protocol, code, message, and URL
   */
  toString(): string {
    return `Response{protocol=${this.#protocol}, code=${this.#code}, message=${this.#message}, url=${this.#request.url}}`
  }
  static Builder: typeof ResponseBuilder;
}

export class ResponseBuilder {
  #request: Request | null = null
  #protocol: Protocol | null = null
  #code = -1
  #message: string | null = null
  #handshake: Handshake | null = null
  #headers = new Headers.Builder()
  #body: ResponseBody | null = null
  #networkResponse: Response | null = null
  #cacheResponse: Response | null = null
  #priorResponse: Response | null = null
  #sentRequestAtMillis: number = 0
  #receivedResponseAtMillis: number = 0
  #exchange: Exchange | null = null

  constructor(response?: Response) {
    if (response === null || response === undefined) {
      return
    }
    this.#request = response.request
    this.#protocol = response.protocol
    this.#code = response.code
    this.#message = response.message
    this.#handshake = response.handshake
    this.#headers = response.headers.newBuilder()
    this.#body = response.body
    this.#networkResponse = response.networkResponse
    this.#cacheResponse = response.cacheResponse
    this.#priorResponse = response.priorResponse
    this.#sentRequestAtMillis = response.sentRequestAtMillis
    this.#receivedResponseAtMillis = response.receivedResponseAtMillis
    this.#exchange = response.exchange
  }
  request(request: Request){
    this.#request = request
    return this
  }

  protocol(protocol: Protocol){
    this.#protocol = protocol
    return this
  }

  code(code: number){
    this.#code = code
    return this
  }

  message(message: string){
    this.#message = message
    return this
  }

  handshake(handshake?: Handshake | null){
    this.#handshake = handshake ?? null
    return this
  }
  /**
   * Sets the header named [name] to [value]. If this request already has any headers
   * with that name, they are all replaced.
   */
  header(name: string, value: string){
    this.#headers.set(name, value)
    return this
  }
  /**
   * Adds a header with [name] to [value]. Prefer this method for multiply-valued
   * headers like "Set-Cookie".
   */
  addHeader(name: string, value: string){
    this.#headers.add(name, value)
    return this
  }

  /** Removes all headers named [name] on this builder. */
  removeHeader(name: string){
    this.#headers.removeAll(name)
    return this
  }
  /** Removes all headers on this builder and adds [headers]. */
  headers(headers: Headers){
    this.#headers = headers.newBuilder()
    return this
  }

  body(body?: ResponseBody | null){
    this.#body = body ?? null
    return this
  }

  networkResponse(networkResponse: Response | null){
    this.#checkSupportResponse("networkResponse", networkResponse)
    this.#networkResponse = networkResponse
    return this
  }

  cacheResponse(cacheResponse: Response | null){
    this.#checkSupportResponse("cacheResponse", cacheResponse)
    this.#cacheResponse = cacheResponse
    return this
  }

  #checkSupportResponse(name: string, response: Response | null) {
    kotlinRequire(response === null || response?.body === null, `${name}.body != null`)
    kotlinRequire(response === null || response?.networkResponse === null,`${name}.networkResponse != null`)
    kotlinRequire(response === null || response?.cacheResponse === null,`${name}.cacheResponse != null`)
    kotlinRequire(response === null || response?.priorResponse === null,`${name}.priorResponse != null`)
  }


  priorResponse(priorResponse?: Response | null){
    this.#checkPriorResponse(priorResponse)
    this.#priorResponse = priorResponse ?? null
    return this
  }

  #checkPriorResponse(response?: Response | null) {
    kotlinRequire(response === null || response === undefined || response.body === null, "priorResponse.body != null")
  }
  sentRequestAtMillis(sentRequestAtMillis: number){
    this.#sentRequestAtMillis = sentRequestAtMillis
    return this
  }

  receivedResponseAtMillis(receivedResponseAtMillis: number){
    this.#receivedResponseAtMillis = receivedResponseAtMillis
    return this
  }

  initExchange(deferredTrailers: Exchange) {
    this.#exchange = deferredTrailers
  }

  build(): Response {
    kotlinCheck(this.#code >= 0, `code < 0: ${this.#code}`)
    if (this.#request === null) {
      throw new Error("builder.request == null")
    }
    if (this.#protocol === null) {
      throw new Error("builder.protocol == null")
    }
    if (this.#message === null) {
      throw new Error("builder.message == null")
    }
    return new Response(
      this.#request,
      this.#protocol,
      this.#message,
      this.#code,
      this.#handshake!,
      this.#headers.build(),
      this.#body,
      this.#networkResponse,
      this.#cacheResponse,
      this.#priorResponse,
      this.#sentRequestAtMillis,
      this.#receivedResponseAtMillis,
      this.#exchange
    )
  }
}

Response.Builder = ResponseBuilder;
